package levelSystem;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Random;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;

import levelSystem.levels.MainMenu;
import mapSystem.Tile;
import mapSystem.MapHandler;
import mapSystem.interaction.MapSounds;
import npcSystem.enemies.BasicEnemy;
import npcSystem.enemies.Enemy;
import npcSystem.mainCharSystem.*;
import tools.Constants;
import tools.Game;
import tools.GameElement;
import tools.ImageHandler;
import tools.Physics;
import tools.Rect;

public class MapLevel extends GameLevel{

    private ArrayList<Enemy> enemies;
    private Rect realCam;
    private Rect virtualCam;
    private final int camWidth = Constants.TILE_WIDTH * 13;
    private final int camHeight = Constants.TILE_HEIGHT * 10;
    private int levelWidth;  // largura do mapa
    private int levelHeight; // altura do mapa
    private int visualLevelWidth;
    private int visualLevelHeight;
    private int screenWidth; // largura da tela
    private int screenHeight;// altura da tela
    private MainChar john;
    
    private String mapName;
    
    private boolean err;
    private boolean enterPressed;
    
    private MapHandler mapHandler;
        
    private BufferedImage healthBar;
    private BufferedImage curHealth;
    
    private BufferedImage pauseScreen;
    private BufferedImage gameOverScreen;
    private int gameOverX;
    private int gameOverY;
        
    private boolean mapChanged;
    
    private int spawnCount;
    private int spawnCountLimit;
    
    public MapLevel(String mn)
    {
    	this(mn, 33, 21);
    }
    public MapLevel(String mn, int x, int y)
    {
    	this(mn, x, y, true);
    }
    public MapLevel(String mn, int x, int y, boolean selfInitiable) {
    	super();
    	
    	game = new Game(false, mapName);
    	
    	gameOver = false;
    	
    	mapName = mn;
    	
    	enemies = null;
    	
    	realCam = null;
    	
    	levelWidth = 0;
    	levelHeight = 0;
    	visualLevelHeight = 0;
    	visualLevelHeight = 0;
    	screenWidth = 0;
    	screenHeight = 0;
    	
    	mapHandler = null;
    	
    	err = false;
    	enterPressed = false;
    	
    	healthBar = ImageHandler.addColorKey(ImageHandler.loadImage("/img/mainChar/life1.png"));
    	curHealth = ImageHandler.addColorKey(ImageHandler.loadImage("/img/mainChar/life2.png"));
    	
    	mapChanged = false;
    	
    	spawnCount = 0;
    	spawnCountLimit = 75; // 2.5s
    	
    	gameOverX = 80;
    	gameOverY = -150;
    	
    	try
    	{
    		MapSounds.init();
    	}catch(ExceptionInInitializerError e)
    	{
    		e.printStackTrace();
    	}
	}
    
    @Override
    public void init()
    {
    	init(0, 0);
    }
	public void init(int x, int y)
	{
    	mapHandler = new MapHandler(this);
		mapHandler.changeLevel(mapName, x, y);

        screenWidth = 780;
        screenHeight = 600;
		
    	calcValues();
    	john = new MainChar(x*Constants.TILE_WIDTH, y*Constants.TILE_HEIGHT, levelWidth, levelHeight, realCam, this);
    	
    	game.saveGame(mapName, x, y);
    	
    	pauseScreen = ImageHandler.loadImage("/img/elements/pauseScreen.png");
    	pauseScreen = ImageHandler.addColorKey(pauseScreen);
    	
    	gameOverScreen = ImageHandler.loadImage("/img/elements/gameOverScreen.png");
    	gameOverScreen = ImageHandler.addColorKey(gameOverScreen);
    	
    	if(songThread == null)
    	{
    		songThread = new Thread(songRun);
    	}
    	
    	InputStream is = MapLevel.class.getResourceAsStream("/sound/mainTheme.mid");
    	if(is == null) System.out.println("Som nao encontrado");
    	else try
    	{
    		song = MidiSystem.getSequence(is);
    		player = MidiSystem.getSequencer();
    	}catch(InvalidMidiDataException e){
    		e.printStackTrace();
    	}catch(MidiUnavailableException e){
    		e.printStackTrace();
    	}catch(IOException e){
    		e.printStackTrace();
    	}
    	
    	songThread.start();
	}
    
    private void setCam(Rect john) {
		realCam.x = john.x - realCam.w/2 + john.w/2;
		realCam.y = john.y - realCam.h/2 + john.h/2;
		
		if(realCam.x < 0) realCam.x = 0;
		if(realCam.x + realCam.w > levelWidth) realCam.x = levelWidth - realCam.w;
		if(realCam.y < 0) realCam.y = 0;
		if(realCam.y + realCam.h > levelHeight) realCam.y = levelHeight - realCam.h;
		
        virtualCam.x = realCam.x - Constants.TILE_WIDTH;
        virtualCam.y = realCam.y - Constants.TILE_HEIGHT;
	}
        
    public int getLevelWidth()
    {
    	return levelWidth;
    }
    public int getLevelHeight()
    {
    	return levelHeight;
    }
    
    public ArrayList<Enemy> getEnemies()
    {
    	return enemies;
    }
    
    public MainChar getMainChar()
    {
    	return john;
    }

    @Override
    public boolean logic() {
        if(err)
        {
            return false;
        }
        if(paused && player.isOpen()) player.stop(); 
        else if(player.isOpen()) player.start();
        if(canChangeLevel && fadeFinished)
        {
        	player.stop();
        	player.close();
        	changeLevel(new MainMenu());
        }else if(retry && fadeFinished)
        {
        	gameOver = false;
        	canChangeLevel = false;
        	fadeFinished = false;
        	retry = false;
        	gameOverY = -150;
        	john.resetKeys();
        	john.reset();
        	mapHandler.forceChangeLevel(game.getNomeMapa(), game.getxMapa(), game.getyMapa());
        }
        if(!gameOver)
        {
        	john.move();
        	
	        int x = (int)(john.getX()+john.getWidth()/2)/Constants.TILE_WIDTH;
	        int y = (int)(john.getY()+john.getHeight()*3/5)/Constants.TILE_HEIGHT;
	        if(!mapHandler.mapIsNull()) mapHandler.checkEvents(x, y, 1);                
	        
	        for(GameElement e : mapHandler.getElements())
	       	{
	        	e.move();
	        }
	        for(int i = 0; i < mapHandler.getElements().size(); i++)
	       	{
	        	if(mapHandler.getElements().get(i).timeOut() ||
	        	   checkCollisionWithAllWalls(mapHandler.getElements().get(i).getRect()) ||
	        	   checkCollisionWithLimits(mapHandler.getElements().get(i).getRect()))
	        	{
	        		mapHandler.getElements().remove(mapHandler.getElements().get(i));
	        	}
	        }
	        
	        for(int i = 0; i < enemies.size(); i++)
	        {
	        	switch(enemies.get(i).logic())
	        	{
	        		case -1:
	        			return false;
	        		case 0:
	        			break;
	        		case 1:
	        			enemies.remove(i);
	        			break;
	        	}
	        }
	        
	        for(Enemy e : enemies)
	        {
	        	if(Physics.checkBlockCollision(john.getMe(), e.getMe()))
	        	{
	        		john.beingHit(e.getxVel(), e.getyVel());
	        	}
	        }
	        for(GameElement e : mapHandler.getElements())
	        {
	        	if(e.getID() == -1) // instakill
	        	{
	        		if(Physics.checkBlockCollision(e.getRect(), john.getMe())) john.die();
	        	}
	        }
        }
	    if(!canChangeLevel)
	    {
	        int playerReturn = john.logic();
	        if(playerReturn < 0) return false;
	        if(playerReturn == 1) gameOver = true;
	        
	        mapHandler.clearShowingTiles();
	        mapHandler.setShowingTiles(virtualCam);
	        
	        spawnCount++;
	        if(spawnCount >= spawnCountLimit)
	        {
	        	spawnCount = 0;
		        Enemy e = spawnEnemy();
		        if(e != null) enemies.add(e);
	        }
	    }
    	
        return true;
    }
    
    public void playerInteraction(int curDir) {
		
    	int x = (int)john.getXView()/Constants.TILE_WIDTH;
        int y = (int)john.getYView()/Constants.TILE_HEIGHT;
        
        mapHandler.checkEvents(x, y, 2);
	}
    public void equipChar(int id) {
    	john.setWeapon(WeaponGetter.getWeapon(id)); 
	}

    @Override
    public void renderLevel(Graphics gg) {
    	
        BufferedImage aux = new BufferedImage(camWidth, camHeight, BufferedImage.TRANSLUCENT);
        Graphics g = aux.getGraphics();
        	        
        setCam(john.getMe());
        if(realCam.w > levelWidth) realCam.x = (levelWidth-realCam.w)/2;
        if(realCam.h > levelHeight) realCam.y = (levelHeight-realCam.h)/2;
        
        if(visualLevelWidth < screenWidth || visualLevelHeight < screenHeight)
        {
	        gg.setColor(Color.black);
	        gg.fillRect(0, 0, screenWidth, screenHeight);
        }
        for(Tile t : mapHandler.getShowingTiles()) // baseMap
        {
            t.draw(g, realCam);
//            	g.drawRect((int)(t.getRect().x - realCam.x), (int)(t.getRect().y - realCam.y), 
//            		(int)(t.getRect().w), (int)(t.getRect().h));
        }
        
        /* Actions */
        if(mapHandler.getActions() != null)
        {
        	for(int i = 0; i < mapHandler.getMapWidth(); i++)
	        {
	        	for(int j = 0; j < mapHandler.getMapHeight(); j++)
	        	{
	        		if(mapHandler.getActions()[i][j] != null)
	        		{
	        			mapHandler.getActions()[i][j].render(g, realCam);
	        		}
	        	}
	        }
        }
        
        /* elements */
        for(GameElement e : mapHandler.getElements())
        {
        	e.draw(g, realCam);
        }
        
        /* Enemies */
        for(int i = 0; i < enemies.size(); i++)
        {
        	if(enemies.size() > i) enemies.get(i).draw(g);
        }

        /* mainChar */
        john.draw(g);
                     
        /* Overmap */
        if(mapHandler.getOvermap() != null)
        {
	        for(int i = 0; i < mapHandler.getOvermap().length; i++)
	        {
	        	for(int j = 0; j < mapHandler.getOvermap()[0].length; j++)
	        	{
	        		if(mapHandler.getOvermap()[i][j] != null)
	        		{
	        			g.drawImage(mapHandler.getOvermap()[i][j], 
	        					(int)(i*Constants.TILE_WIDTH - realCam.x),
	        					(int)(j*Constants.TILE_HEIGHT - realCam.y), null);
	        		}
	        	}
	        }
        }
        
        /* Status screen */
        for(int i = 0; i < john.getMaxHealth(); i++)
        {
        	g.drawImage(healthBar, 16*i + 16 + i*2, 16, null);
        }
        for(int i = 0; i < john.getHealth(); i++)
        {
        	g.drawImage(curHealth, 16*i + 16 + i*2, 16, null);
        }
        
        if(paused)
        {
        	g.drawImage(pauseScreen, 80, 80, null);
        }
        
        /* gameOver */
        if(gameOverScreen != null && gameOver) drawGameOver(g);
        
        if(!fadeFinished)
    	{
    		if(fadeType == 1)
    		{
    			fadeIn(g, aux.getWidth(), aux.getHeight());
    		}else fadeOut(g, aux.getWidth(), aux.getHeight());
    	}
        
        gg.drawImage(aux, 0, 0, screenWidth, screenHeight, null);
        
        if(debugOn)
        {
        	showDebug(gg);
        }
        
        g.dispose();
        gg.dispose();
    }
    private void drawGameOver(Graphics g)
    {
    	gameOverY += 5;
    	if(gameOverY >= 80) gameOverY = 80; 
    	
        g.drawImage(gameOverScreen, gameOverX, gameOverY, null);
        
    }

	@Override
    protected void procUp(int i) {
		if(john != null) john.processKey(KeyEvent.VK_UP, i);
    }
    @Override
    protected void procDown(int i) {
    	if(john != null) john.processKey(KeyEvent.VK_DOWN, i);
    }
    @Override
    protected void procLeft(int i) {
    	if(john != null) john.processKey(KeyEvent.VK_LEFT, i);
    }
    @Override
    protected void procRight(int i) {
    	if(john != null) john.processKey(KeyEvent.VK_RIGHT, i);
    }

    @Override
    protected void procA(int i) {
    	if(john != null) john.processKey(KeyEvent.VK_A, i);
    }
    @Override
    protected void procS(int i) {
    	if(john != null) john.processKey(KeyEvent.VK_S, i);
    }
    @Override
    protected void procD(int i) {
    	if(john != null) john.processKey(KeyEvent.VK_D, i);
    }
    @Override
    protected void procW(int i) {
    	if(john != null) john.processKey(KeyEvent.VK_W, i);
    }

    @Override
    protected void procEnter(int i) {
    	if(!enterPressed &&  i == 1)
    	{
    		enterPressed = true;
	    	if(paused) paused = false;
	    	else paused = true;
    	}
    	if(i == 2)
    	{
    		enterPressed = false;
    	}
    }
    
    public void setCharPosition(double x, double y)
    {
    	/* By tile */
    	john.setPos(x*Constants.TILE_WIDTH, y*Constants.TILE_HEIGHT);
    }
    public ArrayList<Tile> getShowingWalls()
    {
    	return mapHandler.getShowingWalls();
    }
    
    public void calcValues()
    {
    	if(mapHandler.mapIsNull()) err = true;
    	else
    	{
    		
    		levelWidth = mapHandler.getMapWidth()*Constants.TILE_WIDTH;
            levelHeight = mapHandler.getMapHeight()*Constants.TILE_HEIGHT;

            visualLevelWidth = (screenWidth/Constants.TILE_WIDTH)*levelWidth;
            visualLevelHeight = (screenHeight/Constants.TILE_HEIGHT)*levelHeight;
            
            realCam = new Rect(0, 0, camWidth, camHeight);
            virtualCam = new Rect(realCam.x - Constants.TILE_WIDTH,
            		realCam.y - Constants.TILE_HEIGHT,
            		realCam.w + Constants.TILE_WIDTH*2,
            		realCam.h + Constants.TILE_HEIGHT*2);
            
            if(john != null)
            {
            	john.setCam(realCam);
            }
            
            if(enemies == null)
            {
            	enemies = new ArrayList<Enemy>();
            }else enemies.clear();
            
            enterPressed = false;
    	}
    }
    
   	@Override
	protected void showDebug(Graphics g) {
		g.fillRect(screenWidth/2 - 300, 15, 600, 40);
        g.setColor(Color.white);
        g.drawRect(screenWidth/2 - 301, 14, 602, 42);
        g.drawString("curVel: "+john.getVel(), screenWidth/2 - 290, 30);
        g.drawString("cur xVel: "+john.getxVel(), screenWidth/2 - 200, 30);
        g.drawString("cur yVel: "+john.getyVel(), screenWidth/2 - 100, 30);
        g.drawString("cur x: "+john.getMe().x, screenWidth/2 , 30);
        g.drawString("cur y: "+john.getMe().y, screenWidth/2 + 100, 30);
        g.drawString("numEnemies: "+enemies.size(), screenWidth/2 + 190, 30);
        g.drawString("levelWidth: "+levelWidth, screenWidth/2 - 290, 50);
        g.drawString("levelheight: "+levelHeight, screenWidth/2 - 180, 50);
        g.drawString("screenWidth: "+screenWidth, screenWidth/2 - 70, 50);
        g.drawString("screenHeight: "+screenHeight, screenWidth/2 + 50, 50);
        g.drawString("Tile("+(int)john.getMe().x/Constants.TILE_WIDTH+", "+
        		(int)john.getMe().y/Constants.TILE_HEIGHT+")", screenWidth/2 + 180, 50);
	}

	private boolean checkCollisionWithAllWalls(Rect r)
    {
    	for(int i = 0; i < mapHandler.getMapWidth(); i++)
    	{
    		for(int j = 0; j < mapHandler.getMapHeight(); j++)
    		{
    			if(Physics.checkBlockCollision(r, mapHandler.getBaseMap()[i][j].getRect()) && 
    					mapHandler.getBaseMap()[i][j].getType() == 0) return true;
    		}
    	}
    	return false;
    }
	private boolean checkCollisionWithLimits(Rect r)
	{
		if( r.x < 0 || r.y < 0 || r.x+r.w > visualLevelWidth || r.x+r.w > visualLevelHeight ) return true;
		return false;
	}
	
	public void error()
	{
		err = true;
	}
	
	private Enemy spawnEnemy()
	{
		Enemy e = null;
		Random r = new Random(System.nanoTime());
		
		int aux = r.nextInt(1000);
		if(aux > 200) return null;
		
		int x = r.nextInt(screenWidth - Constants.NPC_DEFAULT_SPRITE_WIDTH);
		int y = r.nextInt(screenHeight - Constants.NPC_DEFAULT_SPRITE_HEIGHT);
		
		e = new BasicEnemy(x, y, realCam, this);
		
		while(checkCollisionWithAllWalls(e.getMe()))
		{
			x = r.nextInt(screenWidth - Constants.NPC_DEFAULT_SPRITE_WIDTH);
			y = r.nextInt(screenHeight - Constants.NPC_DEFAULT_SPRITE_HEIGHT);
			
			e.setPos(x, y);
		}
		
		return e;
	}
	
	public void mapChanged()
	{
		MapSounds.changeMap.play();
		mapChanged = true;
	}
	public void mapReady()
	{
		mapChanged = false;
	}
	public boolean mapHasChanged()
	{
		return mapChanged;
	}
	
	@Override
	public void pauseGame() {
		if(!gameOver && john != null)
		{
			john.resetKeys();
			john.reset();
			super.pauseGame();
		}
	}
	
	public void addElement(GameElement e)
	{
		if(mapHandler != null) mapHandler.addElement(e);
	}

	public Game getGame()
	{
		return game;
	}

	public void playButton()
	{
		MapSounds.button.play();
	}
}
